/**
 * 
 */
package de.farbstreich.tozen2.server.games;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import de.farbstreich.tozen2.common.TozenMessageType;
import de.farbstreich.tozen2.common.TozenProtocolMessage;
import de.farbstreich.tozen2.common.TozenProtocolMessage.game;
import de.farbstreich.tozen2.server.GameAlreadyStartedException;
import de.farbstreich.tozen2.server.PlayerThread;
import de.farbstreich.tozen2.server.TozenServer;

/**
 * @author  martin
 * Extends this class and GameTypeAbstract to add a gametype
 */
public abstract class Game {

	/**
	 * @author martin
	 * The possible tiles
	 */
	public enum TileEnum {
		TOWER, KNIGHT, ARCHER;		
	}

	/**
	 * @author martin
	 * Represents a tile. 
	 */
	protected class Tile {
		/**
		 * The player which owns this tile
		 */
		PlayerThread player;
		/**
		 * The type of this tile
		 */
		TileEnum tile;
		
		public Tile(PlayerThread player, TileEnum tile) {
			this.player = player;
			this.tile = tile;
		}
	}
	public final GameTypeAbstract type;
	
	private static int thisCount = 0;
	
	/**
	 * The board matrix. Saves, where are which tiles
	 */
	protected Tile[][] board;
	/**
	 * The server which owns this game
	 */
	protected TozenServer server;
	/**
	 * A unique ID on the server for this game
	 */
	protected final int id;
	/**
	 * The name of this game, which is set the admin
	 */
	protected final String name;
	/**
	 * true - the game is running<br>
	 * false - the games is NOT running
	 */
	protected boolean run;
	/**
	 * The creator of this game
	 */
	protected PlayerThread admin;
	/**
	 * The player, which currently has to move.
	 * Is null, if the game isn't running
	 */
	protected PlayerThread currentPlayer;
	/**
	 * The players in this game, conneted with her IDs
	 */
	protected HashMap<Integer, PlayerThread> players;
	/**
	 * Saves the colors for each player.
	 */
	protected HashMap<PlayerThread, Integer> colors;
	protected ArrayList<PlayerThread> order;
	protected int currentPlayerPosition = 0;
	protected int moveCounter = 0;
	

	
	/**
	 * The constructor is called by the GameType
	 * @param name
	 * @param maxPlayersCount
	 * @param minPlayersCount
	 */
	public Game(String name, PlayerThread admin, TozenServer server, GameTypeAbstract type) {
		this.name = name;
		this.id = Game.thisCount++;
		this.type = type;
		this.admin = admin;
		this.server = server;
		this.order = new ArrayList<PlayerThread>();
		
		this.colors = new HashMap<PlayerThread, Integer>();
		this.players = new HashMap<Integer, PlayerThread>();
		players.put(admin.getPlayerId(), admin);
		this.colors.put(admin, 0);
	}
	
	public int getId() {
		return id;
	}
	
	public boolean isRunning() {
		return run;
	}
	
	public String getName() {
		return name;
	}
	
	public int getPlayersCount(){
		return this.players.size();
	}
	
	public PlayerThread getAdmin() {
		return admin;
	}
	/**
	 * Tries a move. Returns, if the move is valid.
	 * The move is executed, if it's valid.
	 * @param startX 
	 * @param startY
	 * @param targetX
	 * @param targetY
	 * @return A valid move returns null, else a stirng with a help message.
	 */
	public abstract boolean tryMove(Integer startX, Integer startY, Integer targetX, Integer targetY);
	
	
	/**
	 * Executes a move, without validating.
	 * @param startX
	 * @param startY
	 * @param targetX
	 * @param targetY
	 */
	public void move(Integer startX, Integer startY, Integer targetX, Integer targetY) {
		System.out.println("Move:" + startX + "," + startY  + ";" + targetX + "," + targetY);
		
		this.board[targetX][targetY] = this.board[startX][startY];
		this.board[startX][startY] = null;
		this.moveCounter++;
		this.sendPositions();
	}
	
	
	
	/**
	 * Ends the game
	 */
	abstract void winner(PlayerThread p);
	
	/**
	 * Set the next player, and sends his id to all player in this game.
	 */
	abstract void nextPlayer();
	
	/**
	 * Adds a player to the game
	 * @param player the player to add
	 * @throws NoMorePlayerException
	 */
	public void addPlayer(PlayerThread player) throws NoMorePlayerException {
		
		if(hasPlayer(player.getPlayerId()) || players.size() >= this.type.maxPlayersCount || this.run == true)
				throw new NoMorePlayerException();
		else {
			players.put(player.getPlayerId(), player);
			this.colors.put(player, 0);
			this.sendGameInfos();
		}
		
		
	}
	
	/**
	 * Removes a player from the game, and brings him back to the lobby
	 * @param player
	 */
	public void removePlayer(PlayerThread player) {
		this.players.remove(player.getPlayerId());
		
		for(int x = 0; x < this.board.length; x++)
			for(int y = 0; y < this.board[x].length; y++)
				if(this.board[x][y] != null && this.board[x][y].player == player)
					this.board[x][y] = null;
		
		this.order.remove(player);
		
		if(this.currentPlayer != null && player == this.currentPlayer)
			this.nextPlayer();
		
		if(this.currentPlayer != null)
			this.sendPositions();
		
		TozenProtocolMessage msg = new TozenProtocolMessage(TozenMessageType.SURRENDER_GAME);
		player.sendProtocolMessage(msg);
		
		
		
		
		if(this.run == true && this.players.size() == 1)		
			this.winner(player);
		
		if(this.players.size() < 1)		
			this.server.removeGame(this);
		
					
	}
	
	/**
	 * Brings all players back to the lobby
	 */
	public void killGame(){
		for(PlayerThread p: this.players.values())
			p.goToLobby();
	}
	
	/**
	 * Returns true, if this game has a player with "id"
	 * @param id
	 * @return true/false
	 */
	public boolean hasPlayer(int id){
		return players.containsKey(id);
		
			  
	}
	
	/**
	 * Send a protocol message to all players in this game
	 * @param msg
	 */
	public void broadcastProtocolMessage(TozenProtocolMessage msg) {
		for(PlayerThread p : this.players.values())
			p.sendProtocolMessage(msg);
	}
	
	public void sendGameStatstics(PlayerThread p){
		p.sendInfoMessage("Anzahl Zuege:"+moveCounter);
	}
	
	/**
	 * Starts the game
	 * @throws NotRightPlayerCountException
	 * @throws GameAlreadyStartedException
	 */
	public void startGame() throws NotRightPlayerCountException, GameAlreadyStartedException {
		
		if(this.run == true)
			throw new GameAlreadyStartedException();
		
		if(players.size() < this.type.minPlayersCount)
			throw new NotRightPlayerCountException();
		else
			this.run = true;
		
		int i = 0;
		Iterator<PlayerThread> firstIt = this.players.values().iterator();
		PlayerThread first = firstIt.next();
		for(PlayerThread p : this.players.values()) {
			this.colors.put(p, i++);
			this.order.add(p);
		}
		
		
		
		
	}

	/**
	 * Sends the complete game infos to all players
	 */
	public void sendGameInfos() {
		TozenProtocolMessage msg = new TozenProtocolMessage(TozenMessageType.GAME);
		msg.games = new game[1];
		msg.games[0] = msg.new game();
		msg.games[0].name = this.name;
		msg.games[0].title = this.type.title;
		msg.games[0].id = this.id;
		msg.games[0].minPlayersCount = this.type.minPlayersCount;
		
		
		for(PlayerThread p : this.players.values())
			msg.games[0].players.add(msg.new player(p.getPlayerId(),p.getPlayerName(),this.colors.get(p)));
	
		this.broadcastProtocolMessage(msg);
	}

	/**
	 * Sends the id of the current player
	 */
	public void sendCurrentMessage() {
		TozenProtocolMessage msg = new TozenProtocolMessage(TozenMessageType.CURRENT_PLAYER);
		msg.players.add(msg.new player(this.currentPlayer.getPlayerId()));
		
		this.broadcastProtocolMessage(msg);
	}
	
	/**
	 *  Sends all tile positions
	 */
	public void sendPositions() {
		TozenProtocolMessage msg = new TozenProtocolMessage(TozenMessageType.POSITIONS);
		msg.games = new game[1];
		msg.games[0] = msg.new game();
	
		for(int x = 0; x < this.board.length; x++)
			for(int y = 0; y < this.board[x].length; y++)
				if(this.board[x][y] != null)
					msg.games[0].positions.add(
							msg.new position(
									x,
									y,
									this.board[x][y].player.getPlayerId(),
									this.board[x][y].tile.ordinal()));
		
		this.broadcastProtocolMessage(msg);
	}
	
	public void sendMessage(int sender, String message) {
		for(PlayerThread p: this.players.values())
			p.sendMessage(sender, message, true);
	}
	
	//JUST FOR FUN
	public void addTile(int x, int y, int plId, TileEnum t) {
		this.board[x][y] = new Tile(this.players.get(plId), t);
		this.sendPositions();
	}
	
}